/*
 * @(#)DefaultUsfActionInvocation.java $version 2010. 8. 11
 *
 * Copyright 2007-2010 NHN Corp. All rights Reserved. 
 * NHN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package com.nhncorp.usf.core.xwork;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.opensymphony.xwork.ActionContext;
import com.opensymphony.xwork.ActionProxy;
import com.opensymphony.xwork.Result;
import com.opensymphony.xwork.interceptor.Interceptor;
import com.opensymphony.xwork.interceptor.PreResultListener;
import com.opensymphony.xwork.util.OgnlValueStack;

import com.nhncorp.usf.core.config.runtime.Action;
import com.nhncorp.usf.core.config.runtime.PageInfo;
import com.nhncorp.usf.core.config.runtime.ResultPageInfo;
import com.nhncorp.usf.core.config.runtime.ScriptInfo;
import com.nhncorp.usf.core.result.ConditionEvaluateException;
import com.nhncorp.usf.core.result.ResultGenerator;
import com.nhncorp.usf.core.service.ServiceExecutor;
import com.nhncorp.usf.core.service.ServiceExecutor.RESULT_STATUS;
import com.nhncorp.usf.core.servlet.ServletContextHolder;
import com.nhncorp.usf.core.util.ScriptUtil;
import com.nhncorp.usf.core.util.StringUtil;

/**
 * @author Web Platform Development Team
 */
@SuppressWarnings("serial")
public class DefaultUsfActionInvocation implements UsfActionInvocation {
    Log log = LogFactory.getLog(DefaultUsfActionInvocation.class);
    public static final String KEY_FAILURE_CODE = "failurecode";
    public static final String KEY_ERROR_CODE = "";

    /**
     * status of service execution.
     */
    boolean executed;

    /**
     * index of executed interceptor
     */
    private int currentInterceptorIdx = 0;

    /**
     * {@link List} of {@link UsfInterceptor}.
     */
    private List<Interceptor> interceptors;

    /**
     * {@link List} of {@link BeforeResultDispatch} to be executed before dispatching {@link Result}.
     */
    private List<PreResultListener> preResultListeners = new ArrayList<PreResultListener>();
    private ServiceExecutor serviceExecutor;
    private ResultGenerator resultGenerator;
    private PageInfo pageInfo;
    private Action actionInfo;
    private Map<String, Object> dataMap;
    private ResultPageInfo resultPageInfo;
    ActionContext invocationContext;

    /**
     * extract list of interceptor applied to action.
     * if interceptor does not exist, default interceptor will be used.
     */
    public DefaultUsfActionInvocation(ServiceExecutor serviceExecutor, ResultGenerator resultGenerator,
                                      PageInfo pageInfo, Action actionInfo, Map<String, Object> dataStructure) throws Exception {

        XworkInterceptorMapping interceptorMapping = XworkInterceptorMapping.getInstance();
        interceptors = interceptorMapping.getInterceptors(actionInfo, dataStructure);

        this.serviceExecutor = serviceExecutor;
        this.resultGenerator = resultGenerator;

        this.pageInfo = pageInfo;
        this.actionInfo = actionInfo;
        this.dataMap = dataStructure;

        // get ActionContext from current thread and put dataMap and servletRequest.
        invocationContext = ActionContext.getContext();
        invocationContext.put(ActionInvocationHelper.KEY_USF_DATA_STRUCTURE, dataStructure);
    }

    /**
     * exeucte {@link Interceptor}.
     * invoke interceptor and execute service if all of interceptors are executed.
     * 
     * @return the result code
     * @throws Exception the Exception
     */
    public String invoke() throws Exception {
        String resultCode = null;

        if (executed) {
            throw new IllegalStateException("Service is Alreay executed.");
        }

        if (interceptors != null && currentInterceptorIdx < interceptors.size()) {
            Interceptor interceptor = interceptors.get(currentInterceptorIdx++);
            resultCode = interceptor.intercept(this);

            // if interceptor returns notEmptyString, it is failure.
            if (StringUtil.isNotEmpty(resultCode, false)) {
                currentInterceptorIdx--;
            }
        }

        if (!executed) {
            // execute service and create resultPageInfo
            RESULT_STATUS resultStatus = interceptorExecStatus() ? doExecuteService() : doFailureService(resultCode);
            try {
                resultPageInfo = createResultPageInfo(resultStatus);
            } catch (ConditionEvaluateException e) {
                throw e;
            }

            // if action is exected, resultCode is success or failure.
            // below code is useless. It is created because of xwork-interface.
            resultCode = resultStatus.name();

            // beforeResult processing using Interceptor( only executed interceptor)
            if (currentInterceptorIdx > 0) {
                for (PreResultListener preResultListener : preResultListeners) {
                    // resultCode 로 성공이면 success 아니면 failure
                    preResultListener.beforeResult(this, resultStatus.name());
                }
            }

            // dispatch page using ResultPageInfo returned by executed action.
            try {
                resultGenerator.executeResult(this);
            } catch (Exception e) {
                throw e;
            }

            executed = true;
        }

        return resultCode;
    }

    /**
     * Clear.
     */
    public void clear() {
        this.dataMap.clear();
        this.dataMap = null;
        this.interceptors = null;
        this.preResultListeners.clear();
        this.preResultListeners = null;
        this.serviceExecutor = null;
        this.resultGenerator = null;
        this.pageInfo = null;
        this.actionInfo = null;
        this.resultPageInfo = null;
        this.invocationContext = null;
    }

    /**
     * @see com.opensymphony.xwork.ActionInvocation#isExecuted()
     * {@inheritDoc}
     */
    public boolean isExecuted() {
        return executed;
    }

    // -- usf action invocation --------------------------------------------------------------------------------------------------

    /**
     * add {@link BeforeResultDispatch}.
     * 
     * @param preResultListener the PreResultListener
     */
    public void addPreResultListener(PreResultListener preResultListener) {
        preResultListeners.add(0, preResultListener);
    }

    /**
     * Return {@link PageInfo}.
     * 
     * @return the PageInfo object
     */
    public PageInfo getPageInfo() {
        return this.pageInfo;
    }

    /**
     * Gets the interceptors.
     *
     * @return the interceptors
     */
    public List<Interceptor> getInterceptors() {
        return interceptors;
    }

    /**
     * Sets the interceptors.
     *
     * @param interceptors the new interceptors
     */
    public void setInterceptors(List<Interceptor> interceptors) {
        this.interceptors = interceptors;
    }

    /**
     * Return {@link Action}.
     * 
     * @return the Action object
     */
    public Action getActionInfo() {
        return this.actionInfo;
    }

    /**
     * return {@link Map} contained data from client.
     * 
     * @return the data map
     */
    public Map<String, Object> getDataMap() {
        return this.dataMap;
    }

    /**
     * set the failureCode.
     * It will be called by Interceptor and used by method named doServiceExecute.
     * 
     * @param failureCode the failure code
     */
    public void setFailureCode(String failureCode) {
        this.dataMap.put(KEY_FAILURE_CODE, failureCode);
    }

    /**
     * return final {@link ResultPageInfo}
     * 
     * @return the ResultPageInfo
     */
    public ResultPageInfo getResultPageInfo() {
        return this.resultPageInfo;
    }

    // -- ActionContext ----------------------------------------------------------------------------------------------------------

	/**
	 * @see com.opensymphony.xwork.ActionInvocation#getInvocationContext()
	 * {@inheritDoc}
	 */
    public ActionContext getInvocationContext() {
        return invocationContext;
    }

    /**
     * @see com.opensymphony.xwork.ActionInvocation#getAction()
     * {@inheritDoc}
     */
    public Object getAction() {
        throw new UnsupportedOperationException();
    }

    /**
     * @see com.opensymphony.xwork.ActionInvocation#getProxy()
     * {@inheritDoc}
     */
    public ActionProxy getProxy() {
        throw new UnsupportedOperationException();
    }

    /**
     * @see com.opensymphony.xwork.ActionInvocation#getResult()
     * {@inheritDoc}
     */
    public Result getResult() throws Exception {
        throw new UnsupportedOperationException();
    }

    /**
     * @see com.opensymphony.xwork.ActionInvocation#getResultCode()
     * {@inheritDoc}
     */
    public String getResultCode() {
        throw new UnsupportedOperationException();
    }

    /**
     * @see com.opensymphony.xwork.ActionInvocation#getStack()
     * {@inheritDoc}
     */
    public OgnlValueStack getStack() {
        throw new UnsupportedOperationException();
    }

    /**
     * @see com.opensymphony.xwork.ActionInvocation#invokeActionOnly()
     *  {@inheritDoc}
     */
    public String invokeActionOnly() throws Exception {
        throw new UnsupportedOperationException();
    }

    /**
     * @see com.opensymphony.xwork.ActionInvocation#setResultCode(java.lang.String)
     * {@inheritDoc}
     */
    public void setResultCode(String arg0) {
        throw new UnsupportedOperationException();
    }

    // -- private ----------------------------------------------------------------------------------------------------------------

    /**
     * check whether all of interceptor is executed or not.
     * 
     * @return the interceptor execution check
     */
    private boolean interceptorExecStatus() {
        int interceptorSize = 0;
        
        if (interceptors != null) {
            interceptorSize = this.interceptors.size();
        }

        if (log.isDebugEnabled()) {
            log.debug("successInterceptor : " + currentInterceptorIdx + ", interceptorSize : " + interceptorSize);
        }

        if (currentInterceptorIdx != interceptorSize) {
            log.debug("Interceptor is not fully executed. Because of execution failure of "
                    + "interceptor or miss of calling method named interrupt in Interceptor.");
            return false;
        }

        return true;
    }

    /**
     * execute service if all of interceptor is exected.
     * 
     * @return the RESULT_STATUS
     * @throws Exception the Exception
     */
    private RESULT_STATUS doExecuteService() throws Exception {
        return serviceExecutor.execute(actionInfo, dataMap);
    }

    /**
     * Do failure service.
     *
     * @param resultCode the result code
     * @return the rESUL t_ status
     */
    private RESULT_STATUS doFailureService(String resultCode) {
        setFailureCode(resultCode);
        return RESULT_STATUS.failure;
    }

    // -- protected ----------------------------------------------------------------------------------------------------------------
    /**
     * create final {@link ResultPageInfo} using {@link RESULT_STATUS}.
     * 
     * @param resultStatus the RESULT_STATUS
     * @return the ResultPageInfo
     * @throws Exception the Exception
     */
    protected ResultPageInfo createResultPageInfo(RESULT_STATUS resultStatus) throws Exception {
        ScriptInfo pageScriptInfo = pageInfo.getPageCommonScriptInfo();
        ScriptInfo resultScriptInfo = actionInfo.getResultCommonScriptInfo();

        if (pageScriptInfo != null) {
            ScriptUtil.processControlScript(ServletContextHolder.getContextRealPath(), pageScriptInfo, dataMap);
        }

        if (resultScriptInfo != null) {
            ScriptUtil.processControlScript(ServletContextHolder.getContextRealPath(), resultScriptInfo, dataMap);
        }

        if (resultStatus == RESULT_STATUS.success) {
            return resultGenerator.getResultPageInfo(pageInfo.getGlobalSuccessResultInfo(),
                                                     actionInfo.getSuccessResultInfo(), dataMap);
        }

        return resultGenerator.getResultPageInfo(pageInfo.getGlobalFailureResultInfo(),
                                                 actionInfo.getFailureResultInfo(), dataMap);
    }
}
